import pandas as pd
import numpy as np
from category_encoders.target_encoder import TargetEncoder
from sklearn.model_selection import KFold
from sklearn.metrics import auc, roc_curve
from lightgbm import LGBMRegressor

# 导入数据
train = pd.read_csv('train.csv', index_col='id') #训练集(历史数据)
test = pd.read_csv('testA.csv', index_col='id') #测试集(新数据)
target = train.pop('isDefault') #验证集(历史数据)
test = test[train.columns] # columns是列标题，train.columns为将训练集的列标题提取出来
                           # test[]的[]中此时为上面提取出来的列标题，而test[]则是将testA测试集中有上述列标题的数据提取出来
                           # 此举的作用可能是为了防止两者的列标题不一致

# 非数值列
s = train.apply(lambda x:x.dtype) # 生成一个csv文档s，将训练集上的数值转换成对应的数据类型
tecols = s[s=='object'].index.tolist() # s[s=='object']提取s中的非数值类型,index为提取行标题，tolist()为将数组转换为列表，将其命名为tecols

# 模型
def makelgb(): #定义LightBGM模型的超参数
    lgbr = LGBMRegressor(num_leaves=30 # 指定叶子的个数

                        ,max_depth=5 # 指定树的最大深度，默认值为-1，表示不做限制，合理的设置可以防止过拟合。推荐的数值为：[3, 5, 6, 7, 9, 12, 15, 17, 25]。

                        ,learning_rate=0.02 # LightGBM 不完全信任每个弱学习器学到的残差值，为此需要给每个弱学习器拟合的残差值都乘上取值范围在(0, 1]的eta，
                                            # 设置较小的 eta 就可以多学习几个弱学习器来弥补不足的残差。推荐的候选值为：[0.01, 0.015, 0.025, 0.05, 0.1]

                        ,n_estimators=1000 # boosting的迭代次数。默认设置为100。一般根据数据集和特征数据选择100~1000之间。
                                           # 更保守的做法是设置一个较大的值配合early_stopping_round来让模型根据性能自动选择最好的迭代次数。
                                           # 选择比较大的迭代次数会在训练集获得比较好的性能但容易过拟合造成测试集的性能下降。

                        ,subsample_for_bin=5000 # 构建箱的样本数

                        ,min_child_samples=200 # 叶节点样本的最少数量，默认值20，用于防止过拟合

                        ,colsample_bytree=0.2 # 若此参数小于1.0，LightGBM将会在每次迭代中随机选择部分特征(col)，可以用来加速训练及处理过拟合。
                                              # 默认设置为1，一般设置为0.8~1.0之间，防止过拟合。

                        ,reg_alpha=0.1 # L1正则化参数，别名：lambda_l1。默认设置为0。一般经过特征选择后这个参数不会有特别大的差异，
                                       # 如果发现这个参数数值大，则说明有一些没有太大作用的特征在模型内。需要调节来控制过拟合

                        ,reg_lambda=0.1 # L2正则化参数，别名：lambda_l2。默认设置为0。较大的数值会让各个特征对模型的影响力趋于均匀
                                        # 不会有单个特征把持整个模型的表现。需要调节来控制过拟合
                        )
    return lgbr

# 本地验证
kf = KFold(n_splits=10, shuffle=True, random_state=100) # n_splits 表示划分为几块（至少是2）
                                                        # shuffle 表示洗牌操作，也就是是否打乱，默认False，即不打乱，Ture则为打乱
                                                        # random_state 表示是否固定随机起点，一般在 shuffle == True时使用
devscore = []
for tidx, didx in kf.split(train.index): # 利用spilt函数，按训练集的行标题切分数据，切分为tidx(训练集)和didx(验证集)
    tf = train.iloc[tidx]                # iloc函数，通过行号(tidx、didx)来获取行数据
    df = train.iloc[didx]                # 划分后，tf为训练集的数据、df为验证集的数据
    tt = target.iloc[tidx]               # tt为训练集的违约结果
    dt = target.iloc[didx]               # dt为验证集的违约结果
    te = TargetEncoder(cols=tecols)      # 使用目标编码将上述命名为tecols的非数值列表编码为数值列表
    data_train = te.fit_transform(train, target)
    data_test = te.transform(test)
    tf = te.fit_transform(tf, tt)
    df = te.transform(df)
    lgbr = makelgb()                     # model.fit()函数的作用：将训练数据在模型中训练一定迭代次数，返回一个历史训练数据，即loss和测量指标
    lgbr.fit(tf, tt)                     # model.fit(x,y)；x为训练数据的输入,y为训练数据的输出
    pre = lgbr.predict(df)               # model.predict( ) 将输入数据放到已经训练好的模型中，可以得到预测出的输出值
    fpr, tpr, thresholds = roc_curve(dt, pre)  # 利用dt(验证集的违约结果)和pre(验证集)得出roc曲线图的相关值（fpr、tpr、thresholds）
    score = auc(fpr, tpr)                # 用fpr和tpr得出auc值，auc值为roc曲线与x轴围成的面积
    devscore.append(score)               # 将for循环后每一次计算出来auc值加进devscore[]列表中
print(np.mean(devscore))                 # 利用numpy中的均值函数求多个auc值的均值，反映模型的评估效果

# 在整个train集上重新训练，预测test，输出结果
lgbr = makelgb()
te = TargetEncoder(cols=tecols)
tf = te.fit_transform(train, target)
df = te.transform(test)
lgbr.fit(tf, target)
pre = lgbr.predict(df)
pd.Series(pre, name='isDefault', index=test.index).reset_index().to_csv('submit.csv', index=False) # 将整个train集的预测结果输入进命名为submit.csv的文件